package alkaid.oss.local.component;

import static org.springframework.util.StringUtils.cleanPath;
import static org.springframework.util.StringUtils.hasText;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

import javax.annotation.PostConstruct;

import org.apache.tika.Tika;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import alkaid.oss.common.util.Helper;
import alkaid.oss.local.config.OssLocalProperties;
import alkaid.oss.local.core.LocalOssException;
import alkaid.oss.local.domain.FileMetadata;
import alkaid.oss.local.domain.FileMetadataRepository;
import alkaid.oss.local.domain.FileMetadataService;
import cn.hutool.core.lang.Assert;
import lombok.RequiredArgsConstructor;

/**
 * 文件系统存储组件
 * 
 * @author Lucky Yang
 * @since 0.0.1
 */
@Component
@RequiredArgsConstructor
public class FileSystemStorageComponent {
    private final OssLocalProperties ossProperties;
    private final FileMetadataService fileMetadataService;
    private final FileMetadataRepository fileMetadataRepository;

    private Path uploadRootPath;
    private final static Tika TITA = new Tika();

    @PostConstruct
    public void init() throws IOException {
        this.uploadRootPath = ossProperties.getUploadRootPath();

        if (!Files.exists(uploadRootPath)) {
            Files.createDirectories(uploadRootPath);
        }
    }

    /**
     * 存储文件
     * 
     * @param file
     * @return 元数据ID
     */
    @Transactional(rollbackFor = Exception.class)
    public FileMetadata store(MultipartFile file) {
        Assert.notNull(file);

        if (file.isEmpty()) {
            throw new LocalOssException("不能上传空文件");
        }

        final String fileName = cleanPath(file.getOriginalFilename());
        if (!hasText(fileName)) {
            throw new LocalOssException("文件名是必须的");
        }

        try {
            // 根据文件名，生成两级目录
            final String filePath = String.join(File.separator, Helper.generalHashPath(fileName));
            final Path destinationPath = this.uploadRootPath.resolve(filePath);
            if (!Files.exists(destinationPath)) {
                Files.createDirectories(destinationPath);
            }

            // 存储文件元数据
            final FileMetadata fmd = fileMetadataService.save(fileName, filePath,
                    TITA.detect(file.getInputStream()), file.getSize());

            final Path fileFullPath = destinationPath.resolve(String.valueOf(fmd.getId()));
            if (Files.exists(fileFullPath)) {
                throw new LocalOssException("文件已存在");
            }

            try (InputStream fileData = file.getInputStream()) {
                // 存储文件
                Files.copy(fileData, fileFullPath, StandardCopyOption.REPLACE_EXISTING);
                return fmd;
            }

        } catch (IOException e) {
            throw new LocalOssException("存储文件异常");
        }
    }

    /**
     * 下载文件
     * 
     * @param fmd 文件元数据
     * @return
     */
    public FileData download(FileMetadata fmd) {
        Assert.notNull(fmd);

        Path fileFullPath = this.uploadRootPath.resolve(fmd.toRealPath()).resolve(String.valueOf(fmd.getId()));
        try {
            Resource file = new UrlResource(fileFullPath.toUri());

            if (!file.exists()) {
                throw new LocalOssException("文件不存在");
            }

            if (!file.isReadable()) {
                throw new LocalOssException("文件不可读取");
            }

            return FileData.of(file, fmd.getName(), fmd.getContentType());
        } catch (MalformedURLException e) {
            throw new LocalOssException("文件读取失败");
        }
    }

    /**
     * 删除文件
     * 
     * @throws IOException
     */
    public void remove(Long fileMetadataId) throws IOException {
        FileMetadata fmd = fileMetadataService.findById(fileMetadataId);
        fileMetadataRepository.delete(fmd);
        Path fileFullPath = this.uploadRootPath.resolve(fmd.toRealPath()).resolve(String.valueOf(fmd.getId()));
        Files.deleteIfExists(fileFullPath);
    }
}
